﻿using System.Runtime.CompilerServices;
using Autofac;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Smartstore.Core.Seo;
using Smartstore.Core.Seo.Routing;

namespace Smartstore.Core.Content.Menus
{
    public static class INavigatableExtensions
    {
        class PathInfo
        {
            public string RequestPath { get; set; }
            public string RoutePath { get; set; }
        }

        public static bool IsCurrent(this INavigatable navigatable, ActionContext actionContext)
        {
            // TODO: (mh) (core) Test this thoroughly!
            var url = navigatable.GenerateUrl(actionContext)?.ToLower();

            if (url.IsEmpty())
            {
                return false;
            }

            var compare = GetCurrentPathInfo(actionContext);

            if (url == compare.RequestPath || url == compare.RoutePath)
            {
                return true;
            }

            return false;
        }

        private static PathInfo GetCurrentPathInfo(ActionContext actionContext)
        {
            var cacheKey = "CurrentPathInfoForSitemapMatching";

            return actionContext.HttpContext.GetItem(cacheKey, () =>
            {
                var urlPolicy = actionContext.HttpContext.GetUrlPolicy();
                var urlHelper = actionContext.HttpContext.RequestServices.GetRequiredService<IUrlHelper>();

                var info = new PathInfo
                {
                    RequestPath = UrlPolicy.CombineSegments(urlPolicy.PathBase, urlPolicy.Path.Value)
                };

                // Path generated by Routing.
                var routeValues = actionContext.RouteData.Values;

                // We pushed matching UrlRecord instance to RouteValues in SlugRouteTransformer.
                var urlRecord = actionContext.RouteData.Values[SlugRouteTransformer.UrlRecordRouteKey] as UrlRecord;
                if (urlRecord != null)
                {
                    var routeName = urlRecord.EntityName;

                    // Also slug has been pushed to current RouteValues in SlugRouteTransformer class.
                    if (routeValues.ContainsKey(SlugRouteTransformer.SlugRouteKey))
                    {
                        info.RoutePath = urlHelper.RouteUrl(routeName, new { SeName = routeValues[SlugRouteTransformer.SlugRouteKey] });
                    }
                }
                else
                {
                    info.RoutePath = urlHelper.RouteUrl(routeValues);
                }

                return info;
            });
        }

        public static void Action(this INavigatable navigatable, RouteValueDictionary routeValues)
        {
            routeValues.ApplyTo(navigatable, SetAction);
        }

        public static void Action(this INavigatable navigatable, string actionName, string controllerName, RouteValueDictionary routeValues)
        {
            SetAction(navigatable, actionName, controllerName, routeValues);
        }

        public static void Action(this INavigatable navigatable, string actionName, string controllerName, object routeValues)
        {
            navigatable.ControllerName = controllerName;
            navigatable.ActionName = actionName;
            navigatable.SetRouteValues(routeValues);
        }

        public static void Route(this INavigatable navigatable, string routeName, object routeValues)
        {
            navigatable.RouteName = routeName;
            navigatable.SetRouteValues(routeValues);
        }

        public static void Route(this INavigatable navigatable, string routeName, RouteValueDictionary routeValues)
        {
            navigatable.RouteName = routeName;
            navigatable.SetRouteValues(routeValues);
        }

        public static void ApplyTo(this RouteValueDictionary routeValues, INavigatable target, Action<INavigatable, string, string, RouteValueDictionary> callBack)
        {
            Guard.NotNull(target, nameof(target));

            RouteValueDictionary values = new();
            GetActionParams(routeValues, out object actionName, out object controllerName, values);
            callBack(target, (string)actionName, (string)controllerName, values);
        }

        private static void GetActionParams(RouteValueDictionary routeValues, out object actionName, out object controllerName, RouteValueDictionary values)
        {
            routeValues.TryGetValue("action", out actionName);
            routeValues.TryGetValue("controller", out controllerName);
            routeValues.Remove("action");
            routeValues.Remove("controller");
            values.Merge(routeValues);
        }

        public static void ModifyParam(this INavigatable navigatable, string paramName, IEnumerable<string> booleanParamNames = null)
        {
            if (navigatable.ModifiedParam != null)
            {
                navigatable.ModifiedParam.Name = paramName;
                if (booleanParamNames != null)
                {
                    navigatable.ModifiedParam.BooleanParamNames.Clear();
                    navigatable.ModifiedParam.BooleanParamNames.AddRange(booleanParamNames);
                }
            }
        }

        public static void ModifyParam(this INavigatable navigatable, object paramValue)
        {
            if (navigatable.ModifiedParam != null)
            {
                navigatable.ModifiedParam.Value = paramValue;
            }
        }

        public static void Url(this INavigatable navigatable, string value)
        {
            navigatable.Url = value;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static string GenerateUrl(this INavigatable navigatable, ActionContext actionContext, RouteValueDictionary routeValues = null)
        {
            Guard.NotNull(actionContext, nameof(actionContext));
            return navigatable.GenerateUrl(actionContext.HttpContext.GetServiceScope().Resolve<IUrlHelper>(), routeValues);
        }

        public static string GenerateUrl(this INavigatable navigatable, IUrlHelper urlHelper, RouteValueDictionary routeValues = null)
        {
            Guard.NotNull(urlHelper, nameof(urlHelper));

            string str = null;

            if (routeValues != null)
            {
                navigatable.RouteValues.Merge(routeValues);
            }

            routeValues = navigatable.RouteValues;

            var hasParam = false;
            var param = navigatable.ModifiedParam;
            if (param != null && param.HasValue() && param.Value != null)
            {
                routeValues[param.Name] = param.Value;
                hasParam = true;
            }

            if (!string.IsNullOrEmpty(navigatable.RouteName))
            {
                try
                {
                    return urlHelper.RouteUrl(navigatable.RouteName, routeValues);
                }
                catch
                {
                    return null;
                }
            }

            if (!string.IsNullOrEmpty(navigatable.ControllerName) && !string.IsNullOrEmpty(navigatable.ActionName))
            {
                return urlHelper.Action(navigatable.ActionName, navigatable.ControllerName, routeValues, null, null);
            }

            if (!string.IsNullOrEmpty(navigatable.ActionName))
            {
                return urlHelper.Action(navigatable.ActionName, routeValues);
            }

            if (!string.IsNullOrEmpty(navigatable.Url))
            {
                return (!navigatable.Url.StartsWith("~/") ? navigatable.Url : urlHelper.Content(navigatable.Url));
            }

            if (hasParam)
            {
                var booleanParamNames = param.BooleanParamNames;

                foreach (var key in urlHelper.ActionContext.HttpContext.Request.Query.Keys.Where(key => key != null))
                {
                    var value = urlHelper.ActionContext.HttpContext.Request.Query[key].ToString();
                    if (booleanParamNames.Contains(key, StringComparer.InvariantCultureIgnoreCase))
                    {
                        // little hack here due to ugly MVC implementation
                        // find more info here: http://www.mindstorminteractive.com/blog/topics/jquery-fix-asp-net-mvc-checkbox-truefalse-value/
                        if (!string.IsNullOrEmpty(value) && value.Equals("true,false", StringComparison.InvariantCultureIgnoreCase))
                        {
                            value = "true";
                        }
                    }

                    routeValues[key] = value;
                }

                routeValues[param.Name] = param.Value;

                // TODO: (mh) (core) Investigate further!
                var requestContext = urlHelper.ActionContext;
                //if (requestContext.RouteData?.Route != null)
                //{
                //    var virtualPath = requestContext.RouteData.Route.GetVirtualPath(requestContext, routeValues);
                //    if (virtualPath != null)
                //    {
                //        str = VirtualPathUtility.Combine(VirtualPathUtility.AppendTrailingSlash(requestContext.HttpContext.Request.ApplicationPath), virtualPath.VirtualPath);
                //    }
                //    else
                //    {
                //str = urlHelper.GenerateUrl(null, null, null, routeValues, RouteTable.Routes, urlHelper.ActionContext, true);
                str = urlHelper.RouteUrl(null, routeValues);
                //    }
                //}

                return str;
            }

            if (routeValues.Any())
            {
                str = urlHelper.RouteUrl(routeValues);
            }

            return str;
        }

        private static void SetAction(INavigatable navigatable, string actionName, string controllerName, RouteValueDictionary routeValues)
        {
            navigatable.ActionName = actionName;
            navigatable.ControllerName = controllerName;
            navigatable.SetRouteValues(routeValues);
        }

        private static void SetRouteValues(this INavigatable navigatable, object values)
        {
            if (values != null)
            {
                navigatable.RouteValues.Clear();
                navigatable.RouteValues.Merge(values);
            }
        }

        private static void SetRouteValues(this INavigatable navigatable, IDictionary<string, object> values)
        {
            if (values != null)
            {
                navigatable.RouteValues.Clear();
                navigatable.RouteValues.Merge(values);
            }
        }
    }
}
